#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/**
 * characters used for Base64 encoding
 */
const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/**
 * encode three bytes using base64 (RFC 3548)
 *
 * @param triple three bytes that should be encoded
 * @param result buffer of four characters where the result is stored
 */
void _base64_encode_triple(unsigned char triple[3], char result[4]) {
	int tripleValue, i;

	tripleValue = triple[0];
	tripleValue *= 256;
	tripleValue += triple[1];
	tripleValue *= 256;
	tripleValue += triple[2];

	for (i = 0; i < 4; i++) {
		result[3 - i] = BASE64_CHARS[tripleValue % 64];
		tripleValue /= 64;
	}
}

/**
 * encode an array of bytes using Base64 (RFC 3548)
 *
 * @param source the source buffer
 * @param sourcelen the length of the source buffer
 * @param target the target buffer
 * @param targetlen the length of the target buffer
 * @return 1 on success, 0 otherwise
 */
int base64_encode(unsigned char *source, size_t sourcelen, char *target, size_t targetlen) {
	/* check if the result will fit in the target buffer */
	if ((sourcelen + 2) / 3 * 4 > targetlen - 1)
		return 0;

	/* encode all full triples */
	while (sourcelen >= 3) {
		_base64_encode_triple(source, target);
		sourcelen -= 3;
		source += 3;
		target += 4;
	}

	/* encode the last one or two characters */
	if (sourcelen > 0) {
		unsigned char temp[3];
		memset(temp, 0, sizeof (temp));
		memcpy(temp, source, sourcelen);
		_base64_encode_triple(temp, target);
		target[3] = '=';
		if (sourcelen == 1)
			target[2] = '=';

		target += 4;
	}

	/* terminate the string */
	target[0] = 0;

	return 1;
}

/**
 * determine the value of a base64 encoding character
 *
 * @param base64char the character of which the value is searched
 * @return the value in case of success (0-63), -1 on failure
 */
int _base64_char_value(char base64char) {
	if (base64char >= 'A' && base64char <= 'Z')
		return base64char - 'A';
	if (base64char >= 'a' && base64char <= 'z')
		return base64char - 'a' + 26;
	if (base64char >= '0' && base64char <= '9')
		return base64char - '0' + 2 * 26;
	if (base64char == '+')
		return 2 * 26 + 10;
	if (base64char == '/')
		return 2 * 26 + 11;
	return -1;
}

/**
 * decode a 4 char base64 encoded byte triple
 *
 * @param quadruple the 4 characters that should be decoded
 * @param result the decoded data
 * @return lenth of the result (1, 2 or 3), 0 on failure
 */
int _base64_decode_triple(char quadruple[4], unsigned char *result) {
	int i, triple_value, bytes_to_decode = 3, only_equals_yet = 1;
	int char_value[4];

	for (i = 0; i < 4; i++)
		char_value[i] = _base64_char_value(quadruple[i]);

	/* check if the characters are valid */
	for (i = 3; i >= 0; i--) {
		if (char_value[i] < 0) {
			if (only_equals_yet && quadruple[i] == '=') {
				/* we will ignore this character anyway, make it something
				 * that does not break our calculations */
				char_value[i] = 0;
				bytes_to_decode--;
				continue;
			}
			return 0;
		}
		/* after we got a real character, no other '=' are allowed anymore */
		only_equals_yet = 0;
	}

	/* if we got "====" as input, bytes_to_decode is -1 */
	if (bytes_to_decode < 0)
		bytes_to_decode = 0;

	/* make one big value out of the partial values */
	triple_value = char_value[0];
	triple_value *= 64;
	triple_value += char_value[1];
	triple_value *= 64;
	triple_value += char_value[2];
	triple_value *= 64;
	triple_value += char_value[3];

	/* break the big value into bytes */
	for (i = bytes_to_decode; i < 3; i++)
		triple_value /= 256;
	for (i = bytes_to_decode - 1; i >= 0; i--) {
		result[i] = triple_value % 256;
		triple_value /= 256;
	}

	return bytes_to_decode;
}

/**
 * decode base64 encoded data
 *
 * @param source the encoded data (zero terminated)
 * @param target pointer to the target buffer
 * @param targetlen length of the target buffer
 * @return length of converted data on success, -1 otherwise
 */
size_t base64_decode(char *source, unsigned char *target, size_t targetlen) {
	char *src, *tmpptr;
	char quadruple[4], tmpresult[3];
	int i, tmplen = 3;
	size_t converted = 0;

	/* concatinate '===' to the source to handle unpadded base64 data */
	src = (char *) malloc(strlen(source) + 5);
	if (src == NULL)
		return -1;
	strcpy(src, source);
	strcat(src, "====");
	tmpptr = src;

	/* convert as long as we get a full result */
	while (tmplen == 3) {
		/* get 4 characters to convert */
		for (i = 0; i < 4; i++) {
			/* skip invalid characters - we won't reach the end */
			while (*tmpptr != '=' && _base64_char_value(*tmpptr) < 0)
				tmpptr++;

			quadruple[i] = *(tmpptr++);
		}

		/* convert the characters */
		tmplen = _base64_decode_triple(quadruple, tmpresult);

		/* check if the fit in the result buffer */
		if (targetlen < tmplen) {
			free(src);
			return -1;
		}

		/* put the partial result in the result buffer */
		memcpy(target, tmpresult, tmplen);
		target += tmplen;
		targetlen -= tmplen;
		converted += tmplen;
	}

	free(src);
	return converted;
}
